﻿// CRenderEngine.cs
// Author: Gant Briseno
#region Using Statements
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Casters.source
{
    /// <summary>
    /// Used for all rendering
    /// Setup for running on a separate thread
    /// </summary>
    static public class CRenderEngine
    {
        /// <summary>
        /// Represents all data we need to draw a frame
        /// </summary>
        private class RenderFrameBatch
        {
            public SpriteSortMode m_SortMode;
            public Matrix m_CameraMatrix;
            public BlendState m_BlendMode;

            /// <summary>
            /// Interface for all drawables
            /// </summary>
            interface IDrawInfo
            {
                /// <summary>
                /// Renders the item
                /// </summary>
                /// <param name="_spriteBatch">The SpriteBatch to render to</param>
                void RenderInfo(SpriteBatch _spriteBatch);
            }

            /// <summary>
            /// Minimum amount of information need to draw a string
            /// Uses SpriteBatch.DrawString #1
            /// </summary>
            private class DrawStringInfoMin : IDrawInfo
            {
                private SpriteFont font;
                private string text;
                private Vector2 position;
                private Color color;

                /// <summary>
                /// Sets all data for the string
                /// </summary>
                /// <param name="_font">Font to use when drawing</param>
                /// <param name="_text">The actual text</param>
                /// <param name="_postition">Top left position of string</param>
                /// <param name="_color">Color to draw the string</param>
                public void SetStringInfo(SpriteFont _font, string _text, Vector2 _postition, Color _color)
                {
                    font = _font;
                    text = _text;
                    position = _postition;
                    color = _color;
                }

                /// <summary>
                /// Draws the string
                /// </summary>
                /// <param name="_spriteBatch">The SpriteBatch to draw to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.DrawString(font, text, position, color);
                }
            }

            /// <summary>
            /// Maximum amount of information needed to draw a string
            /// Uses SpriteBatch.DrawString #3
            /// </summary>
            private class DrawStringInfoMax : IDrawInfo
            {
                private SpriteFont font;
                private string text;
                private Vector2 position;
                private Color color;
                private float rotation;
                private Vector2 origin;
                private float scale;
                private SpriteEffects effects;
                private float layerDepth;

                /// <summary>
                /// Sets all data for drawing a string
                /// </summary>
                /// <param name="_font">Font to use for the string</param>
                /// <param name="_text">The actual text for the string</param>
                /// <param name="_position">Position for the top left corner of the string</param>
                /// <param name="_color">The color to draw the string</param>
                /// <param name="_rotation">How much the string is rotated, in radians</param>
                /// <param name="_origin">The origin of the rotation</param>
                /// <param name="_scale">Factor to scale the string by</param>
                /// <param name="_effects">Special effects to apply to the string</param>
                /// <param name="_layerDepth">Which layer to draw the string on</param>
                public void SetStringInfo(SpriteFont _font, string _text, Vector2 _position, Color _color, float _rotation,
                    Vector2 _origin, float _scale, SpriteEffects _effects, float _layerDepth)
                {
                    font = _font;
                    text = _text;
                    position = _position;
                    color = _color;
                    rotation = _rotation;
                    origin = _origin;
                    scale = _scale;
                    effects = _effects;
                    layerDepth = _layerDepth;
                }

                /// <summary>
                /// Draws the string
                /// </summary>
                /// <param name="_spriteBatch">SpriteBatch to draw the string to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.DrawString(font, text, position, color, rotation, origin, scale, effects, layerDepth);
                }
            }

            /// <summary>
            /// Minimum amount of information to draw a sprite
            /// Uses SpriteBatch.Draw #2
            /// </summary>
            private class DrawSpriteInfoMin : IDrawInfo
            {
                private Texture2D texture;
                private Vector2 position;
                private Color color;

                /// <summary>
                /// Sets all the data for drawing a sprite
                /// </summary>
                /// <param name="_texture">Texture used to draw the sprite(will use whole texture)</param>
                /// <param name="_position">Position of top left corner where to draw</param>
                /// <param name="_color">Color to draw the sprite</param>
                public void SetSpriteInfo(Texture2D _texture, Vector2 _position, Color _color)
                {
                    texture = _texture;
                    position = _position;
                    color = _color;
                }

                /// <summary>
                /// Draws the sprite
                /// </summary>
                /// <param name="_spriteBatch">SpriteBatch to draw sprite to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.Draw(texture, position, color);
                }
            }

            /// <summary>
            /// Information needed to draw a sprite using a rectangle for it's destination
            /// Uses SpriteBatch.Draw #1
            /// </summary>
            private class DrawSpriteInfoDestRect : IDrawInfo
            {
                private Texture2D texture;
                private Rectangle destinationRectangle;
                private Color color;

                /// <summary>
                /// Sets the data needed to draw a sprite
                /// </summary>
                /// <param name="_texture">The texture to use(will use whole texture)</param>
                /// <param name="_destinationRectangle">Rectangle on screen to fill with the texture</param>
                /// <param name="_color">Color to draw the sprite</param>
                public void SetSpriteInfo(Texture2D _texture, Rectangle _destinationRectangle, Color _color)
                {
                    texture = _texture;
                    destinationRectangle = _destinationRectangle;
                    color = _color;
                }

                /// <summary>
                /// Draws the sprite
                /// </summary>
                /// <param name="_spriteBatch">SpriteBatch to draw sprite to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.Draw(texture, destinationRectangle, color);
                }
            }

            /// <summary>
            /// Information needed to draw a sprite using a source rectangle with minimum data
            /// Uses SpriteBatch.Draw #4
            /// </summary>
            private class DrawSpriteInfoSrcRect : IDrawInfo
            {
                private Texture2D texture;
                private Vector2 position;
                private Rectangle sourceRectangle;
                private Color color;

                /// <summary>
                /// Sets all the data for the sprite
                /// </summary>
                /// <param name="_texture">Texture used to draw the sprite</param>
                /// <param name="_position">Position of the top left corner of the sprite</param>
                /// <param name="_sourceRectangle">Rectangle on the texture to use for drawing</param>
                /// <param name="_color">Color to use to draw</param>
                public void SetSpriteInfo(Texture2D _texture, Vector2 _position, Rectangle _sourceRectangle, Color _color)
                {
                    texture = _texture;
                    position = _position;
                    sourceRectangle = _sourceRectangle;
                    color = _color;
                }

                /// <summary>
                /// Draws the sprite
                /// </summary>
                /// <param name="_spriteBatch">SpriteBatch to draw to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.Draw(texture, position, sourceRectangle, color);
                }
            }

            /// <summary>
            /// Maximum information needed to draw a sprite
            /// Uses SpriteBatch.Draw #6
            /// </summary>
            private class DrawSpriteInfoFull : IDrawInfo
            {
                private Texture2D texture;
                private Vector2 position;
                private Rectangle sourceRectangle;
                private Color color;
                private float rotation;
                private Vector2 origin;
                private float scale;
                private SpriteEffects effects;
                private float layerDepth;

                /// <summary>
                /// Sets all information for drawing the sprite
                /// </summary>
                /// <param name="_texture">Texture used to draw from</param>
                /// <param name="_position">Top left corner of the sprite</param>
                /// <param name="_sourceRectangle">Rectangle on texture to use for drawing</param>
                /// <param name="_color">Color to draw the sprite</param>
                /// <param name="_rotation">Rotation of the sprite, in radians</param>
                /// <param name="_origin">Origin of the rotation</param>
                /// <param name="_scale">Factor at which to scale the sprite</param>
                /// <param name="_effects">Special effects for the sprite</param>
                /// <param name="_layerDepth">Layer to draw the sprite</param>
                public void SetSpriteInfo(Texture2D _texture, Vector2 _position, Rectangle _sourceRectangle, Color _color,
                    float _rotation, Vector2 _origin, float _scale, SpriteEffects _effects, float _layerDepth)
                {
                    texture = _texture;
                    position = _position;
                    sourceRectangle = _sourceRectangle;
                    color = _color;
                    rotation = _rotation;
                    origin = _origin;
                    scale = _scale;
                    effects = _effects;
                    layerDepth = _layerDepth;
                }

                /// <summary>
                /// Draws the sprite
                /// </summary>
                /// <param name="_spriteBatch">SpriteBatch to draw the image to</param>
                public void RenderInfo(SpriteBatch _spriteBatch)
                {
                    _spriteBatch.Draw(texture, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
                }
            }

            // Render List
            private List<IDrawInfo> m_DrawInfoList = new List<IDrawInfo>();

            /// <summary>
            /// Adds a DrawSpriteInfo object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_texture">Texture to use</param>
            /// <param name="_position">Top left corner of where to draw</param>
            /// <param name="_sourceRectangle">The rectangle to use on the texture</param>
            /// <param name="_color">The color to draw the sprite</param>
            /// <param name="_rotation">Rotation of the iamge, in radians</param>
            /// <param name="_origin">Origin of the rotation</param>
            /// <param name="_scale">Factor at which to scale the sprite</param>
            /// <param name="_effects">Special effects for the sprite</param>
            /// <param name="_layerDepth">Layer to draw the sprite on</param>
            public void AddSpriteInfo(Texture2D _texture, Vector2 _position, Rectangle _sourceRectangle, Color _color,
                float _rotation, Vector2 _origin, float _scale, SpriteEffects _effects, float _layerDepth)
            {
                DrawSpriteInfoFull spriteInfo = new DrawSpriteInfoFull();
                spriteInfo.SetSpriteInfo(_texture, _position, _sourceRectangle, _color, _rotation, _origin, _scale, _effects, _layerDepth);
                m_DrawInfoList.Add(spriteInfo);
            }

            /// <summary>
            /// Adds a DrawSpriteInfo object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_texture">Texture to use</param>
            /// <param name="_position">Top left corner of where to draw</param>
            /// <param name="_color">The color to draw the sprite</param>
            public void AddSpriteInfo(Texture2D _texture, Vector2 _position, Color _color)
            {
                DrawSpriteInfoMin spriteInfo = new DrawSpriteInfoMin();
                spriteInfo.SetSpriteInfo(_texture, _position, _color);
                m_DrawInfoList.Add(spriteInfo);
            }

            /// <summary>
            /// Adds a DrawSpriteInfo object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_texture">Texture to use</param>
            /// <param name="_position">Tope left corner of where to draw</param>
            /// <param name="_sourceRectangle">The rectangle to use on the texture</param>
            /// <param name="_color">The color to draw the sprite</param>
            public void AddSpriteInfo(Texture2D _texture, Vector2 _position, Rectangle _sourceRectangle, Color _color)
            {
                DrawSpriteInfoSrcRect spriteInfo = new DrawSpriteInfoSrcRect();
                spriteInfo.SetSpriteInfo(_texture, _position, _sourceRectangle, _color);
                m_DrawInfoList.Add(spriteInfo);
            }

            /// <summary>
            /// Adds a DrawSpriteInfo object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_texture">Texture to use</param>
            /// <param name="_destinationRectangle">Rectangle on screen where to draw image(will scale to fit)</param>
            /// <param name="_color">The color to draw the sprite</param>
            public void AddSpriteInfo(Texture2D _texture, Rectangle _destinationRectangle, Color _color)
            {
                DrawSpriteInfoDestRect spriteInfo = new DrawSpriteInfoDestRect();
                spriteInfo.SetSpriteInfo(_texture, _destinationRectangle, _color);
                m_DrawInfoList.Add(spriteInfo);
            }

            /// <summary>
            /// Adds a DrawStringInfoMin object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_font">Font to use</param>
            /// <param name="_text">Text to use</param>
            /// <param name="_position">Where to draw the string</param>
            /// <param name="_color">Color to draw the string</param>
            public void AddStringInfo(SpriteFont _font, string _text, Vector2 _position, Color _color)
            {
                DrawStringInfoMin stringInfo = new DrawStringInfoMin();
                stringInfo.SetStringInfo(_font, _text, _position, _color);
                m_DrawInfoList.Add(stringInfo);
            }

            /// <summary>
            /// Adds a DrawStringInfoMax object to the list, filled out with parameters passed in
            /// </summary>
            /// <param name="_font">Font to use</param>
            /// <param name="_text">Text to use</param>
            /// <param name="_position">Where to draw the string</param>
            /// <param name="_color">Color to darw the string</param>
            /// <param name="_rotation">Rotation of the string, in radians</param>
            /// <param name="_origin">Origin of the rotation</param>
            /// <param name="_scale">Factor to scale the string by</param>
            /// <param name="_effects">Special effects to use for the string</param>
            /// <param name="_layerDepth">Layer to draw the string on</param>
            public void AddStringInfo(SpriteFont _font, string _text, Vector2 _position, Color _color, float _rotation,
                Vector2 _origin, float _scale, SpriteEffects _effects, float _layerDepth)
            {
                DrawStringInfoMax stringInfo = new DrawStringInfoMax();
                stringInfo.SetStringInfo(_font, _text, _position, _color, _rotation, _origin, _scale, _effects, _layerDepth);
                m_DrawInfoList.Add(stringInfo);
            }

            /// <summary>
            /// Draws all items in the batch
            /// </summary>
            /// <param name="_spriteBatch">SpriteBatch to draw everything to</param>
            public void RenderFrame(SpriteBatch _spriteBatch)
            {
                foreach (IDrawInfo currentSprite in m_DrawInfoList)
                    currentSprite.RenderInfo(_spriteBatch);
            }
        }

        private static bool m_bIsRunning = true;
        private static Effect[] m_ShaderEffects;
        private static List<RenderFrameBatch> m_FrameBatches = new List<RenderFrameBatch>();
        private static RenderFrameBatch m_TransferBatch;
        private static SpriteBatch m_spriteBatch;
        private static GraphicsDevice m_GraphicsDevice;
        private static Color m_ClearColor;

        static public void BeginBatchTransfer()
        {
            m_TransferBatch = new RenderFrameBatch();
            m_TransferBatch.m_SortMode = SpriteSortMode.Immediate;
            m_TransferBatch.m_CameraMatrix = Matrix.Identity;
            m_TransferBatch.m_BlendMode = BlendState.AlphaBlend;
        }

        static public void SetBatchBlendMode(BlendState _blend)
        {
            m_TransferBatch.m_BlendMode = _blend;
        }

        static public void BeginSprite()
        {
            m_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
        }
        static public void EndSprite()
        {
            m_spriteBatch.End();
        }

        /// <summary>
        /// Sets the sort mode used for the SpriteBatch
        /// </summary>
        /// <param name="_sortMode"></param>
        static public void SetBatchSortMode(SpriteSortMode _sortMode)
        {
            m_TransferBatch.m_SortMode = _sortMode;
        }

        /// <summary>
        /// Sets the matrix to transform the SpriteBatch and all the sprites
        /// </summary>
        /// <param name="_transformationMatrix"></param>
        static public void SetBatchTransformationMatrix(Matrix _transformationMatrix)
        {
            m_TransferBatch.m_CameraMatrix = _transformationMatrix;
        }

        /// <summary>
        /// Stops all data going to the RenderEngine and adds it to a queue waiting
        /// to be rendered
        /// </summary>
        static public void EndBatchTransfer()
        {
            lock (m_FrameBatches)
            {
                if (m_FrameBatches.Count == 5)
                    m_FrameBatches.RemoveAt(0);

                m_FrameBatches.Add(m_TransferBatch);
                m_TransferBatch = null;
            }
        }

        /// <summary>
        /// Adds a sprite to the current batch
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="position">Top left corner of where to draw the sprite</param>
        /// <param name="sourceRectangle">Rectangle to use on the texture</param>
        /// <param name="color">Color to draw the sprite</param>
        /// <param name="rotation">Rotation of the sprite</param>
        /// <param name="origin">Origin of the rotation</param>
        /// <param name="scale">Factor to scale the sprite</param>
        /// <param name="effects">Special effects to use on the sprite</param>
        /// <param name="layerDepth">Layer to draw the sprite on</param>
        static public void Draw(Texture2D texture, Vector2 position, Rectangle sourceRectangle, Color color,
            float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
        {
            m_TransferBatch.AddSpriteInfo(texture, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
        }

        /// <summary>
        /// Adds a sprite to the current batch
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="position">Top left corner of where to draw the sprite</param>
        /// <param name="color">Color to draw the sprite</param>
        static public void Draw(Texture2D texture, Vector2 position, Color color)
        {
            m_TransferBatch.AddSpriteInfo(texture, position, color);
        }

        /// <summary>
        /// Adds a sprite to the current batch
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="position">Top left corner of where to draw the sprite</param>
        /// <param name="sourceRectangle">Rectangle to use on the texture</param>
        /// <param name="color">Color to draw the sprite</param>
        static public void Draw(Texture2D texture, Vector2 position, Rectangle sourceRectangle, Color color)
        {
            m_TransferBatch.AddSpriteInfo(texture, position, sourceRectangle, color);
        }

        /// <summary>
        /// Adds a sprite to the current batch
        /// </summary>
        /// <param name="texture">Texture to use</param>
        /// <param name="destinationRectangle">Rectangle to fill on screen</param>
        /// <param name="color">Color to draw the sprite</param>
        static public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)
        {
            m_TransferBatch.AddSpriteInfo(texture, destinationRectangle, color);
        }

        /// <summary>
        /// Adds a string to the current batch
        /// </summary>
        /// <param name="font">The font to use</param>
        /// <param name="text">The text to use</param>
        /// <param name="position">Where to draw the string</param>
        /// <param name="color">Color to draw the string</param>
        static public void DrawString(SpriteFont font, string text, Vector2 position, Color color)
        {
            m_TransferBatch.AddStringInfo(font, text, position, color);
        }

        /// <summary>
        /// Adds a string to the current batch
        /// </summary>
        /// <param name="font">The font to use</param>
        /// <param name="text">The text to use</param>
        /// <param name="position">Where to draw the string</param>
        /// <param name="color">Color to draw the string</param>
        /// <param name="rotation">Rotation of the string</param>
        /// <param name="origin">Origin of the rotation</param>
        /// <param name="scale">Factor to scale the string by</param>
        /// <param name="effects">Special effect to use on the string</param>
        /// <param name="layerDepth">Layer to draw the string on</param>
        static public void DrawString(SpriteFont font, string text, Vector2 position, Color color, float rotation,
            Vector2 origin, float scale, SpriteEffects effects, float layerDepth)
        {
            m_TransferBatch.AddStringInfo(font, text, position, color, rotation, origin, scale, effects, layerDepth);
        }

        /// <summary>
        /// Stops the RenderEngine as soon as it finishes the current frame
        /// </summary>
        static public void KillRenderEngine()
        {
            m_bIsRunning = false;
        }

        /// <summary>
        /// Stores all shaders
        /// </summary>
        /// <param name="allEffects">Array of shaders to store</param>
        static public void LoadShaderEffects(Effect[] allEffects)
        {
            m_ShaderEffects = allEffects;
        }

        /// <summary>
        /// Returns a stored effect
        /// </summary>
        /// <param name="shaderEffect">Shader to retrieve</param>
        /// <returns></returns>
        static public Effect RequestEffect(int shaderEffect)
        {
            return m_ShaderEffects[shaderEffect];
        }

        /// <summary>
        /// Begins a shader
        /// </summary>
        /// <param name="shaderEffect"></param>
        static public void BeginEffect(int shaderEffect)
        {
            // TODO: shader effects
            /*
            BasicEffect temp = new BasicEffect();
            temp.
            m_ShaderEffects[shaderEffect].Begin();
                * */
        }

        /// <summary>
        /// Ends a shader
        /// </summary>
        /// <param name="shaderEffect"></param>
        static public void EndEffect(int shaderEffect)
        {
            // TODO: shader effects
        }

        /// <summary>
        /// Sets the default background color
        /// </summary>
        /// <param name="color">Sets background color; Default is black</param>
        static public void SetClearColor(Color color)
        {
            m_ClearColor = color;
        }

        /// <summary>
        /// Sets the device the RenderEngine will render to
        /// </summary>
        /// <param name="graphicsDevice"></param>
        static public void SetGraphicsDevice(GraphicsDevice graphicsDevice)
        {
            m_GraphicsDevice = graphicsDevice;
        }

        static private SpriteFont m_DebugFont;
        static public void SetDebugFont(SpriteFont spriteFont)
        {
            m_DebugFont = spriteFont;
        }

#if DEBUG
        static int lastTime = 0;
        static double fElapsedTime = 0.0;
        static double fCountDown = 0.0;
        static double FPS = 0.0;
        static int aFPS = 0;
        static int framesPast = 0;
        static int aFramesPast = 0;
#endif

        /// <summary>
        /// Sets up some default values
        /// </summary>
        static public void Initialize()
        {
            m_ClearColor = Color.CornflowerBlue;
            m_spriteBatch = new SpriteBatch(m_GraphicsDevice);
        }

        /// <summary>
        /// Temp function to use before multi-threading
        /// </summary>
        static public void UpdateRenderer()
        {
            while (m_bIsRunning)
            {
#if DEBUG
                int thisTime = System.Environment.TickCount;
                fElapsedTime = (double)(thisTime - lastTime) * 0.001;
                lastTime = thisTime;

                if (fCountDown < 0.0)
                {
                    FPS = framesPast;
                    aFPS = aFramesPast;
                    fCountDown = 1.0;
                    framesPast = 0;
                    aFramesPast = 0;
                }
                else
                {
                    fCountDown -= fElapsedTime;
                    ++framesPast;
                }
#endif
                RenderFrameBatch currentRenderBatch = null;
                lock (m_FrameBatches)
                {
                    if (m_FrameBatches.Count != 0)
                    {
                        currentRenderBatch = m_FrameBatches[0];
                        m_FrameBatches.RemoveAt(0);
#if DEBUG
                        ++aFramesPast;
#endif
                    }
                }

                if (currentRenderBatch == null)
                    break;

                m_GraphicsDevice.Clear(m_ClearColor);

                
                m_spriteBatch.Begin(currentRenderBatch.m_SortMode, currentRenderBatch.m_BlendMode,
                    null, null, null, null,
                    currentRenderBatch.m_CameraMatrix);
                
                //m_spriteBatch.Begin(currentRenderBatch.m_SortMode, currentRenderBatch.m_BlendMode);

                currentRenderBatch.RenderFrame(m_spriteBatch);

#if DEBUG
                if (m_DebugFont != null)
                {
                    m_spriteBatch.DrawString(m_DebugFont, "FPS: " + aFPS.ToString() + " / " + FPS.ToString(), new Vector2(11.0f, 11.0f) + CGame.GetInstance().m_Camera.m_vPosition, Color.Black);
                    m_spriteBatch.DrawString(m_DebugFont, "FPS: " + aFPS.ToString() + " / " + FPS.ToString(), new Vector2(10.0f, 10.0f) + CGame.GetInstance().m_Camera.m_vPosition, Color.White);
                }
#endif
                m_spriteBatch.End();
            }
        }
    }
}
